﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;

namespace WentStateMachine
{
    public class Generator
    {
        StateMachineController controller;
        AnimatorController animatorController;

        public Generator(StateMachineController controller, AnimatorController animatorController)
        {
            this.controller = controller;
            this.animatorController = animatorController;
        }

        public void Generate()
        {
            clearObject();
            controller.parameters = generateParams(animatorController.parameters);
            controller.layers = generateLayer(animatorController.layers);
        }

        private Layer[] generateLayer(AnimatorControllerLayer[] layers)
        {
            if (layers == null)
                return null;

            var result = new Layer[layers.Length];
            for (var i = 0; i < layers.Length; i++)
            {
                result[i] = createLayer(layers[i]);
            }
            return result;
        }

        private Layer createLayer(AnimatorControllerLayer layer)
        {
            return new LayerGenerator(controller, layer).Generate();
        }

        private Parameter[] generateParams(AnimatorControllerParameter[] animaParams)
        {
            if (animaParams == null)
            {
                return null;
            }
            var result = new Parameter[animaParams.Length];
            for (var i = 0; i < animaParams.Length; i++)
            {
                result[i] = createParam(animaParams[i]);
            }
            return result;
        }

        private Parameter createParam(AnimatorControllerParameter param)
        {
            return new Parameter()
            {
                name = param.name,
                type = (ParameterType)param.type,
                defaultFloat = param.defaultFloat,
                defaultBool = param.defaultBool,
                defaultInt = param.defaultInt
            };
        }

        private void clearObject()
        {
            if(controller.layers != null)
            {
                foreach(var layer in controller.layers)
                {
                    if (layer.stateMachine)
                    {
                        clearState(layer.stateMachine);
                        clearStateMachine(layer.stateMachine);
                    }
                }
            }
        }

        private void clearBehaviours(StateBehaviour[] behaviours)
        {
            if (behaviours == null)
                return;
            foreach(var behaviour in behaviours)
                AssetDatabase.RemoveObjectFromAsset(behaviour);
        }

        private void clearState(StateMachine stateMachine)
        {
            if(stateMachine.States != null)
            {
                foreach(var state in stateMachine.States)
                {
                    clearBehaviours(state.behaviours);
                    AssetDatabase.RemoveObjectFromAsset(state);
                }
            }

            if(stateMachine.ChildStateMachine != null)
            {
                foreach(var child in stateMachine.ChildStateMachine)
                {
                    clearState(child);
                }
            }
        }

        private void clearStateMachine(StateMachine stateMachine)
        {
            AssetDatabase.RemoveObjectFromAsset(stateMachine);

            if (stateMachine.ChildStateMachine != null)
            {
                foreach (var child in stateMachine.ChildStateMachine)
                {
                    clearStateMachine(child);
                }
            }
        }
    }

    public class GeneratorContext
    {
        public StateMachine baseStateMachine;
        public string baseStateMachineName;
        public Dictionary<string, StateMachine> stateStateMachineNameMap;
        public Dictionary<int, StateMachine> stateStateMachineMap;
    }

    public class LayerGenerator
    {
        readonly StateMachineController controller;
        readonly AnimatorControllerLayer layer;
        readonly GeneratorContext context;

        public LayerGenerator(StateMachineController controller, AnimatorControllerLayer animatorControllerLayer)
        {
            this.controller = controller;
            layer = animatorControllerLayer;
            context = new GeneratorContext()
            {
                baseStateMachineName = animatorControllerLayer.stateMachine.name,
                stateStateMachineNameMap = new Dictionary<string, StateMachine>(),
                stateStateMachineMap = new Dictionary<int, StateMachine>()
            };
        }

        public Layer Generate()
        {
            Layer result = new Layer()
            {
                name = layer.name,
                stateMachine = createRootStateMachine(layer.stateMachine)
            };
            return result;
        }

        private StateMachine createRootStateMachine(AnimatorStateMachine aniStateMachine)
        {
            var result = ScriptableObject.CreateInstance<StateMachine>();
            AssetDatabase.AddObjectToAsset(result, controller);
            context.baseStateMachine = result;
            result.Name = aniStateMachine.name;
            result.States = generateState(result, aniStateMachine.states);
            result.DefaultState = result.FindStateIndexByName(aniStateMachine.defaultState.name);
            result.ChildStateMachine = generateStateMachine(result, aniStateMachine.stateMachines);
            result.AnyStateTransition = generateTransitions(result, aniStateMachine.anyStateTransitions);
            result.EntryTransition = generateTransitions(result, aniStateMachine.entryTransitions);

            result.DoInChildrenStateMachine(stateMachine =>
            {
                var animStateMachine = findAnimStateMachineByName(stateMachine.Name);
                stateMachine.AnyStateTransition = generateTransitions(stateMachine, animStateMachine.anyStateTransitions);
                stateMachine.EntryTransition = generateTransitions(stateMachine, animStateMachine.entryTransitions);
            });

            foreachStateAndCreateTransitions(result, aniStateMachine);
            foreachChildStateMachine(result, aniStateMachine, (s,a)=> {
                foreachStateAndCreateTransitions(s, a);
            });

            return result;
        }

        private void foreachStateAndCreateTransitions(StateMachine root, AnimatorStateMachine animRoot)
        {
            if (root.States == null)
                return;
            for(var i = 0; i < root.States.Length; i++)
            {
                root.States[i].Transitions = generateTransitions(root, animRoot.states[i].state.transitions);
            }
        }

        private void foreachChildStateMachine(StateMachine root, AnimatorStateMachine animRoot, Action<StateMachine, AnimatorStateMachine> action)
        {
            if (root.ChildStateMachine == null)
                return;
            for (var i = 0; i < root.ChildStateMachine.Length; i++)
            {
                action(root.ChildStateMachine[i], animRoot.stateMachines[i].stateMachine);
            }

            for (var i = 0; i < root.ChildStateMachine.Length; i++)
            {
                foreachChildStateMachine(root.ChildStateMachine[i], animRoot.stateMachines[i].stateMachine, action);
            }
        }

        private StateMachine[] generateStateMachine(StateMachine parent, ChildAnimatorStateMachine[] aniStateMachine)
        {
            if (aniStateMachine == null)
                return null;

            StateMachine[] result = new StateMachine[aniStateMachine.Length];
            for(var i = 0; i < result.Length; i++)
            {
                result[i] = createStateMachine(parent, aniStateMachine[i].stateMachine);
            }
            return result;
        }

        private StateMachine createStateMachine(StateMachine parent, AnimatorStateMachine aniStateMachine)
        {
            var result = ScriptableObject.CreateInstance<StateMachine>();
            AssetDatabase.AddObjectToAsset(result, parent);
            result.Name = aniStateMachine.name;
            result.States = generateState(result, aniStateMachine.states);
            result.DefaultState = result.FindStateIndexByName(aniStateMachine.defaultState.name);
            result.ChildStateMachine = generateStateMachine(result, aniStateMachine.stateMachines);
            result.parentStateMachine = parent;
            return result;
        }

        private State[] generateState(StateMachine stateMachine, ChildAnimatorState[] states)
        {
            if (states == null)
                return null;

            State[] results = new State[states.Length];
            for(var i = 0; i < states.Length; i++)
            {
                results[i] = createState(stateMachine, states[i].state);
            }
            return results;
        }

        private State createState(StateMachine stateMachine, AnimatorState animatorState)
        {
            var state = ScriptableObject.CreateInstance<State>();
            AssetDatabase.AddObjectToAsset(state, stateMachine);
            state.Name = animatorState.name;
            state.behaviours = generateStateBehaviours(state, animatorState); 
            return state;
        }

        private Transition[] generateTransitions(StateMachine parent, AnimatorTransitionBase[] transitions)
        {
            if (transitions == null)
                return null;

            var result = new Transition[transitions.Length];
            for (var i = 0; i < transitions.Length; i++)
            {
                result[i] = createTransition(parent, transitions[i]);
            }
            return result;
        }

        private Transition createTransition(StateMachine parent, AnimatorTransitionBase transition)
        {
            List<int> state;
            if (transition.destinationState)
            {
                state = context.baseStateMachine.FindStateReferenceByName(transition.destinationState.name);
                state.Reverse();
            }
            else
                state = null;

            List<int> stateMachine;
            if (transition.destinationStateMachine)
            {
                stateMachine = context.baseStateMachine.FindStateMachineReferenceByName(transition.destinationStateMachine.name);
                stateMachine.Reverse();
            }
            else
                stateMachine = null;

            return new Transition()
            {
                destinationState = state == null ? null : state.ToArray(),
                conditions = generateConditions(transition.conditions),
                destinationStateMachine = stateMachine == null ? null : stateMachine.ToArray()
            };
        }

        private AnimatorStateMachine findAnimStateMachineByName(string name)
        {
            if (layer.stateMachine.name == name)
                return layer.stateMachine;
            return findAnimStateMachineByNameInChildren(name, layer.stateMachine);
        }

        private AnimatorStateMachine findAnimStateMachineByNameInChildren(string name, AnimatorStateMachine stateMachine)
        {
            foreach(var child in stateMachine.stateMachines)
            {
                if (child.stateMachine.name == name)
                    return child.stateMachine;
            }

            foreach (var child in stateMachine.stateMachines)
            {
                var result = findAnimStateMachineByNameInChildren(name, child.stateMachine);
                if (result != null)
                    return result;
            }
            return null;
        }

        private Condition[] generateConditions(AnimatorCondition[] conditions)
        {
            if (conditions == null)
                return null;

            var result = new Condition[conditions.Length];
            for (var i = 0; i < conditions.Length; i++)
            {
                result[i] = createCondition(conditions[i]);
            }
            return result;
        }

        private Condition createCondition(AnimatorCondition condition)
        {
            return new Condition()
            {
                mode = (ConditionMode)condition.mode,
                parameter = condition.parameter,
                threshold = condition.threshold
            };
        }

        private StateBehaviour[] generateStateBehaviours(State state, AnimatorState animState)
        {
            if (animState.behaviours == null)
                return null;
            List<StateBehaviour> behaviours = new List<StateBehaviour>();
            foreach (var behaviour in animState.behaviours)
            {
                var be = behaviour as WentStateMachineBehaviour;
                if (be != null && be.StateBehaviour != null)
                {
                    var target = ScriptableObject.Instantiate(be.StateBehaviour);
                    target.name = be.StateBehaviour.name;
                    AssetDatabase.AddObjectToAsset(target, state);
                    behaviours.Add(target);
                }
            }
            return behaviours.ToArray();
        }
    }
}

